package org.budo.criteria.parser;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.budo.criteria.CriteriaParser;
import org.budo.criteria.SqlUnit;
import org.budo.graph.annotation.SpringGraph;
import org.budo.support.fastjson.Fastjson;
import org.budo.support.lang.util.MapUtil;
import org.budo.support.lang.util.NumberUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.time.Time;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Slf4j
public class BudoCriteriaParser implements CriteriaParser {
    @SpringGraph
    @Override
    public SqlUnit parse(Class<?> type, String filter) {
        if (null == filter || filter.trim().isEmpty() || !filter.startsWith("{") || !filter.endsWith("}")) {
            log.warn("#24 parse, type=" + type + ", filter=" + filter + ", this=" + this);
            return new SqlUnit();
        }

        JSONObject jsonObject = Fastjson.parseObject(filter);
        int size = jsonObject.size();
        if (size < 0 || size > 1) {
            throw new RuntimeException("#18 根条件只允许有一个");
        }

        SqlUnitBuffer sqlUnitBuffer = new SqlUnitBuffer();
        this.parseOperator(sqlUnitBuffer, type, jsonObject);
        return new SqlUnit(sqlUnitBuffer.getSql(), sqlUnitBuffer.getParameters());
    }

    @SuppressWarnings("unchecked")
    public SqlUnitBuffer parseOperator(SqlUnitBuffer sqlUnitBuffer, Class<?> entityType, Map<String, Object> filterMap) {
        Entry<String, Object> firstEntry = MapUtil.firstEntry(filterMap);

        if (null == firstEntry) {
            log.error("#43 firstEntry=" + firstEntry + ", filterMap=" + filterMap);
            return sqlUnitBuffer;
        }

        String operator = firstEntry.getKey();

        if ("$is_null".equalsIgnoreCase(operator)) {
            String field = this.fieldName(entityType, (String) firstEntry.getValue());
            return sqlUnitBuffer.appendSql(field + " IS NULL ");
        }

        if ("$is_not_null".equalsIgnoreCase(operator)) {
            String field = this.fieldName(entityType, (String) firstEntry.getValue());

            return sqlUnitBuffer.appendSql(field + " IS NOT NULL ");
        }

        if ("$eq".equalsIgnoreCase(operator)) {
            Entry<String, Object> val = MapUtil.firstEntry((Map) firstEntry.getValue());
            String field = this.fieldName(entityType, val.getKey());

            Object fieldValue = this.fieldValue(val.getValue());
            String parameter = sqlUnitBuffer.parameter(fieldValue);

            return sqlUnitBuffer.appendSql(field + "=" + parameter);
        }

        if ("$ne".equalsIgnoreCase(operator)) {
            Entry<String, Object> val = MapUtil.firstEntry((Map) firstEntry.getValue());
            String fieldName = this.fieldName(entityType, val.getKey());

            Object fieldValue = this.fieldValue(val.getValue());
            String parameter = sqlUnitBuffer.parameter(fieldValue);

            return sqlUnitBuffer.appendSql(fieldName + "!=" + parameter);
        }

        if ("$in".equalsIgnoreCase(operator)) {
            Entry<String, Object> val = MapUtil.firstEntry((Map) firstEntry.getValue());

            String fieldName = this.fieldName(entityType, val.getKey());

            Object fieldValue = this.fieldValue(val.getValue());
            String parameter = sqlUnitBuffer.parameter(fieldValue);

            return sqlUnitBuffer.appendSql(fieldName + " IN ( " + parameter + " )");
        }

        if ("$not_in".equalsIgnoreCase(operator)) {
            Entry<String, Object> val = MapUtil.firstEntry((Map) firstEntry.getValue());
            String fieldName = this.fieldName(entityType, val.getKey());

            Object fieldValue = this.fieldValue(val.getValue());
            String parameter = sqlUnitBuffer.parameter(fieldValue);

            return sqlUnitBuffer.appendSql(fieldName + " NOT IN ( " + parameter + " )");
        }

        if ("$gt".equalsIgnoreCase(operator)) {
            Entry<String, Object> val = MapUtil.firstEntry((Map) firstEntry.getValue());
            String fieldName = this.fieldName(entityType, val.getKey());

            Object fieldValue = this.fieldValue(val.getValue());
            String parameter = sqlUnitBuffer.parameter(fieldValue);

            return sqlUnitBuffer.appendSql(fieldName + ">" + parameter);
        }

        if ("$lt".equalsIgnoreCase(operator)) {
            Entry<String, Object> val = MapUtil.firstEntry((Map) firstEntry.getValue());
            String fieldName = this.fieldName(entityType, val.getKey());

            Object fieldValue = this.fieldValue(val.getValue());
            String parameter = sqlUnitBuffer.parameter(fieldValue);

            return sqlUnitBuffer.appendSql(fieldName + "<" + parameter);
        }

        if ("$ge".equalsIgnoreCase(operator)) {
            Entry<String, Object> val = MapUtil.firstEntry((Map) firstEntry.getValue());
            String fieldName = this.fieldName(entityType, val.getKey());

            Object fieldValue = this.fieldValue(val.getValue());
            String parameter = sqlUnitBuffer.parameter(fieldValue);

            return sqlUnitBuffer.appendSql(fieldName + ">=" + parameter);
        }

        if ("$le".equalsIgnoreCase(operator)) {
            Entry<String, Object> val = MapUtil.firstEntry((Map) firstEntry.getValue());
            String fieldName = this.fieldName(entityType, val.getKey());

            Object fieldValue = this.fieldValue(val.getValue());
            String parameter = sqlUnitBuffer.parameter(fieldValue);

            return sqlUnitBuffer.appendSql(fieldName + "<=" + parameter);
        }

        if ("$like".equalsIgnoreCase(operator)) {
            Entry<String, Object> val = MapUtil.firstEntry((Map) firstEntry.getValue());
            String key = val.getKey();
            Object value = val.getValue();

            String fieldName = this.fieldName(entityType, key);

            String parameter = sqlUnitBuffer.parameter(value);
            return sqlUnitBuffer.appendSql(fieldName + " LIKE " + parameter);
        }

        if ("$not_like".equalsIgnoreCase(operator)) {
            Entry<String, Object> val = MapUtil.firstEntry((Map) firstEntry.getValue());
            String fieldName = this.fieldName(entityType, val.getKey());
            Object value = val.getValue();

            String parameter = sqlUnitBuffer.parameter(value);
            return sqlUnitBuffer.appendSql(fieldName + " NOT LIKE " + parameter);
        }

        if ("$and".equalsIgnoreCase(operator)) {
            return this.and(sqlUnitBuffer, entityType, firstEntry);
        }

        if ("$or".equalsIgnoreCase(operator)) {
            JSONArray jsonArray = (JSONArray) firstEntry.getValue();
            int len = jsonArray.size();

            sqlUnitBuffer.appendSql(" ( ");

            for (int i = 0; i < len; i++) {
                if (i > 0) {
                    sqlUnitBuffer.appendSql(" OR ");
                }

                Map<String, Object> eachMap = (Map<String, Object>) jsonArray.get(i);
                this.parseOperator(sqlUnitBuffer, entityType, eachMap);
            }

            return sqlUnitBuffer.appendSql(" ) ");
        }

        throw new RuntimeException("#67 operator=" + operator + ", this=" + this);
    }

    private SqlUnitBuffer and(SqlUnitBuffer sqlUnitBuffer, Class<?> entityType, Entry<String, Object> firstEntry) {
        JSONArray jsonArray = (JSONArray) firstEntry.getValue();
        int size = jsonArray.size();

        // 数据预处理
        List<Map<String, Object>> items = new ArrayList<Map<String, Object>>();
        for (int i = 0; i < size; i++) {
            Object first = jsonArray.get(i);

            if (null == first) {
                continue;
            }

            if (first instanceof Map) {
                Map<String, Object> map = (Map<String, Object>) first;
                if (null != map && !map.isEmpty()) {
                    items.add(map);
                }
                continue;
            }

            if (first instanceof String //
                    && StringUtil.isNotNullOrEmpty((String) first)) {
                JSONObject parse = Fastjson.parseObject((String) first);
                if (null != parse && !parse.isEmpty()) {
                    items.add(parse);
                }
                continue;
            }

            log.error("#176 eachMap=null, first=" + first + ", firstEntry=" + firstEntry);
        }

        // SQL 拼接
        sqlUnitBuffer.appendSql(" ( ");

        int len = items.size();
        for (int i = 0; i < len; i++) {
            if (i > 0) {
                sqlUnitBuffer.appendSql(" AND ");
            }

            Map<String, Object> each = items.get(i);
            this.parseOperator(sqlUnitBuffer, entityType, each);
        }

        return sqlUnitBuffer.appendSql(" ) ");
    }

    /**
     * 可以扩展这个方法，用以替换值
     */
    public Object fieldValue(Object value) {
        if (null == value || !(value instanceof String)) {
            return value;
        }

        String val = (String) value;
        if (StringUtil.startsWith(val, "$_") && StringUtil.endsWith(val, "_DAYS_AGO")) {
            String _val = val.replace("$_", "").replace("_DAYS_AGO", "");
            Integer days = NumberUtil.toInteger(_val);
            return Time.now().plusDay(0 - days).toTimestamp();
        }

        return value;
    }

    /**
     * 可扩展这个方法，用以处理属性名和列名不同的情况
     */
    protected String fieldName(Class<?> type, String field) {
        return field;
    }
}
